﻿using XB.Core.MemoryStore.Logger;
using XB.Core.MemoryStore.MemoryStore;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace XB.Core.MemoryStore.TimeSchduler
{
    public class TimedTaskScheduler
    {
        private List<IMemoryStore> _tasks = new List<IMemoryStore>();
        private SequentialTimer _timer;
        public bool Inited { get; private set; }
        public string Name { get; set; }

        public TimedTaskScheduler(string name)
        {
            Name = name;
        }

        public void EnsureRunning()
        {
            if (_timer == null)
            {
                _timer = new SequentialTimer(TimedoutCallback, 1000);
            }
        }

        public void RegisterTask(IMemoryStore task)
        {
            _tasks.Add(task);
        }

        public void ForceRun()
        {
            _tasks.ForEach(t => t.Timer.ForceRun());
        }

        public void TimedoutCallback(Object state)
        {
            if (Inited)
            {
                Run();
            }
            else
            {
                RunWithMultiThread();
            }
        }

        public void Run()
        {
            var hasError = false;

            var startTime = DateTime.Now;
            var executedTasks = _tasks.Where(t =>
            {
                try
                {
                    if (t.Timer.TimeOut(startTime))
                    {
                        var spanMs = Perf(t.Refresh);
                        LoggerFactory.Logger.TraceInfo("{0} scheduler: Task {1} DONE, cost {2}ms", Name, t.Name, spanMs);
                        return true;
                    }
                }
                catch (Exception ex)
                {
                    hasError = true;
                    LoggerFactory.Logger.Error(string.Format("{0} scheduler: Error happened to execute task: {1}", Name, t.Name), ex);
                }

                return false;
            }).ToList();

            var finishedTime = DateTime.Now;
            executedTasks.ForEach(t => t.Timer.Reset(finishedTime));

            if (!Inited && !hasError)
            {
                Inited = true;
            }

            if (executedTasks.Count > 0)
            {
                GC.Collect(2, GCCollectionMode.Optimized, false);
                LoggerFactory.Logger.TraceInfo("{0} scheduler: All tasks DONE, cost {1}ms", Name, (int)((DateTime.Now - startTime).TotalMilliseconds));
            }
        }

        private void RunWithMultiThread()
        {
            var hasError = false;

            var startTime = DateTime.Now;

            var executedTasks = new ConcurrentBag<IMemoryStore>();

            foreach (var kv in _tasks.GroupBy(g => g.Level).OrderBy(g => g.Key))
            {
                Parallel.ForEach(kv, new ParallelOptions { MaxDegreeOfParallelism = 10 }, t =>
                {
                    try
                    {
                        if (t.Timer.TimeOut(startTime))
                        {
                            var spanMs = Perf(t.Refresh);
                            LoggerFactory.Logger.TraceInfo("{0} scheduler: Task {1} DONE, cost {2}ms", Name, t.Name, spanMs);
                            executedTasks.Add(t);
                        }
                    }
                    catch (Exception ex)
                    {
                        hasError = true;
                        LoggerFactory.Logger.Error(string.Format("{0} scheduler: Error happened to execute task: {1}", Name, t.Name), ex);
                    }
                });
            }

            var finishedTime = DateTime.Now;

            foreach (var t in executedTasks)
            {
                t.Timer.Reset(finishedTime);
            }

            if (!Inited && !hasError)
            {
                Inited = true;
            }

            if (executedTasks.Count > 0)
            {
                GC.Collect(2, GCCollectionMode.Optimized, false);
                LoggerFactory.Logger.TraceInfo("{0} scheduler: All tasks DONE, cost {1}ms", Name, (int)((DateTime.Now - startTime).TotalMilliseconds));
            }
        }

        private int Perf(Action action)
        {
            var start = DateTime.Now;
            action();
            return (int)(DateTime.Now - start).TotalMilliseconds;
        }

    }
}
